﻿using System;
using System.IO;
using System.Net;
using System.Threading;

namespace CalcSharp.Core.IO
{
    public delegate void UpdateProgress(long BytesDone, long TotalBytes);
    public delegate void DownloadComplete();
    public delegate void ErrorHandle(string ErrorMesage);

    /// <summary>
    /// A Simple HTTP, HTTPS, FTP File downloader class
    /// </summary>
    public class Downloader: IDisposable
    {
        #region Private fields
        private Thread Dload;
        private string Url;
        private TFTPClient TFTP;
        private Stream Target, Response;
        private HttpWebRequest webRequest;
        private HttpWebResponse webResponse;
        private FtpWebRequest ftpRequest;
        private FtpWebResponse ftpResponse;
        private long fileSize;
        private bool error;
        #endregion

        #region Events
        /// <summary>
        /// Event occures, when some progress is made
        /// </summary>
        public event UpdateProgress OnProgress;
        /// <summary>
        /// Event occures, when the download is complete
        /// </summary>
        public event DownloadComplete OnDownloadComplete;
        /// <summary>
        /// Event occures, when error happens
        /// </summary>
        public event ErrorHandle OnError;
        #endregion

        #region Constructors
        /// <summary>
        /// Creates a new instance of the Downloader class
        /// </summary>
        /// <param name="Url">the file to get. Eg.: http://localhost/file.zip</param>
        /// <param name="Dest">The Destination stream, to write contents of the url</param>
        public Downloader(string Url, Stream Dest)
        {
            this.error = false;
            this.Url = Url;
            this.Target = Dest;
            this.Dload = new Thread(Download);
        }
        /// <summary>
        /// Creates a new instance of the Downloader class
        /// </summary>
        /// <param name="Url">the file to get. Eg.: http://localhost/file.zip</param>
        /// <param name="Dest">a file name. The contents of the url will be written into the file</param>
        public Downloader(string Url, string Dest) : this(Url, File.Create(Dest)) { }
        #endregion

        #region Public Methods
        /// <summary>
        /// Starts the download;
        /// </summary>
        public void StartDownload()
        {
            if (!Dload.IsAlive) Dload.Start();
        }
        /// <summary>
        /// Aborts the download
        /// </summary>
        public void AbortDownload()
        {
            if (Dload != null)
            {
                if (Dload.IsAlive)
                {
                    error = true;
                    Dload.Abort();
                    Dload = null;
                    this.Dispose();
                }
            }
        }
        /// <summary>
        /// Resrumes a paused download or pauses a running download
        /// </summary>
        public void PauseOrResrume()
        {
            if (Dload != null)
            {
                if (Dload.ThreadState == ThreadState.Suspended) Dload.Resume();
                else if (Dload.ThreadState == ThreadState.Running) Dload.Suspend();
            }
        }
        #endregion

        #region Public Fields
        public long FileSize
        {
            get { return fileSize; }
        }

        public string FileUrl
        {
            get { return Url; }
        }
        #endregion

        #region Private Methods
        private static byte[] setupbuffer(Int64 Filesize)
        {
            int buffers = 4096;
            if (Filesize > 100 * 1048576) buffers = 1048576; //1Mb buffer
            else if (Filesize > 50 * 1048576) buffers = 512 * 1024; //512kb buffer;
            else if (Filesize > 20 * 1048576) buffers = 128 * 1024; // 128kb buffer
            else if (Filesize > 5242880) buffers = 16 * 1024; //16kb buffer;
            return new byte[buffers];
        }

        private void setupHTTP(string url, NetworkCredential UserPass)
        {
            webRequest = null;
            webResponse = null;
            webRequest = (HttpWebRequest)WebRequest.Create(url);
            if (WebRequest.GetSystemWebProxy() != null) webRequest.Proxy = WebRequest.GetSystemWebProxy();
            webRequest.Credentials = UserPass;
            if (url.StartsWith("https://")) ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
            webResponse = (HttpWebResponse)webRequest.GetResponse();
            if (webResponse.ResponseUri.ToString() != url)
            {
                setupHTTP(webResponse.ResponseUri.ToString(), UserPass);
            }
            fileSize = webResponse.ContentLength;
            Response = webResponse.GetResponseStream();
        }

        private void setupFTP(string url, NetworkCredential UserPass)
        {
            ftpRequest = null;
            ftpResponse = null;
            // get file size ->
            ftpRequest = (FtpWebRequest)WebRequest.Create(url);
            ftpRequest.Credentials = UserPass;
            if (WebRequest.GetSystemWebProxy() != null) ftpRequest.Proxy = WebRequest.GetSystemWebProxy();
            ftpRequest.Method = WebRequestMethods.Ftp.GetFileSize;
            ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
            fileSize = ftpResponse.ContentLength;
            // download ->
            ftpRequest = (FtpWebRequest)WebRequest.Create(url);
            ftpRequest.Credentials = UserPass;
            if (WebRequest.GetSystemWebProxy() != null) ftpRequest.Proxy = WebRequest.GetSystemWebProxy();
            ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile;
            ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
            Response = ftpResponse.GetResponseStream();
        }

        private void Download()
        {
            bool isftp;
            NetworkCredential UserPass;
            string[] tmp1, userpass;
            string url;

            Response = null;
            isftp = Url.StartsWith("ftp://");

            tmp1 = Url.Split('@'); //-> tmp1[1] path
            if (tmp1.Length < 2) UserPass = new NetworkCredential("anonymous", "csharpdltool@csharpdltool.com");
            else
            {
                userpass = tmp1[0].Split(':');
                UserPass = new NetworkCredential(userpass[1].Replace("//", @""), userpass[2]);
            }
            using (WebClient wcDownload = new WebClient())
            {
                Thread.Sleep(100);
                try
                {
                    try
                    {
                        if (!isftp)
                        {
                            this.setupHTTP(Url, UserPass);
                            url = webResponse.ResponseUri.ToString();
                        }
                        else this.setupFTP(Url, UserPass);
                        int bytesSize = 0;
                        byte[] downBuffer = setupbuffer(fileSize);

                        if (Dload.IsAlive)
                        {
                            while ((bytesSize = Response.Read(downBuffer, 0, downBuffer.Length)) > 0)
                            {
                                if (this.OnProgress != null) OnProgress(Target.Length, fileSize);
                                Target.Write(downBuffer, 0, bytesSize);
                            }
                        }
                    }
                    catch (WebException exp)
                    {
                        if (this.OnError != null) OnError(exp.Message);
                        error = true;
                        return;
                    }
                }
                catch (IOException exp)
                {
                    if (this.OnError != null) OnError(exp.Message);
                    error = true;
                    return;
                }
                finally
                {
                    if (Response != null)
                    {
                        Response.Flush();
                        Response.Close();
                        Response = null;
                    }
                    if (this.OnDownloadComplete != null && !error) OnDownloadComplete();
                }
            }
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool dispose)
        {
            if (dispose)
            {
                if (Dload != null)
                {
                    if (Dload.IsAlive) Dload.Abort();
                    Dload = null;
                }
                webRequest = null;
                webResponse = null;
                ftpRequest = null;
                ftpResponse = null;
                if (Target != null)
                {
                    Target.Dispose();
                    Target = null;
                }
                if (Response != null)
                {
                    Response.Dispose();
                    Response.Close();
                }
            }
        }

        #endregion
    }
}
